package br.mikhas.util.jdbc.tx;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.WeakHashMap;

import javax.sql.DataSource;

import br.mikhas.util.Assert;
import br.mikhas.util.jdbc.JdbcException;
import br.mikhas.util.jdbc.JdbcUtils;

/**
 * 
 * @author Mikhail Domanoski
 * 
 */
public final class TransactionUtils {

	/**
	 * Holds the current used connections
	 */
	private static final ThreadLocal<Map<DataSource, Connection>> connections = new ThreadLocal<Map<DataSource, Connection>>();

	/**
	 * Holds the transaction synchronization keys
	 */
	private static final ThreadLocal<Object> synchronization = new ThreadLocal<Object>();

	// Utility class
	private TransactionUtils() {

	}

	/**
	 * Gets a transaction synchonized connection from the providen
	 * {@link DataSource}
	 * 
	 * @param dataSource
	 *            The datasource to get the connection
	 * @return A SQL connection which is being used by the current active
	 *         connections
	 */
	public static Connection getConnection(DataSource dataSource) {

		try {
			Connection con = doGetConnection(dataSource);
			return con;
		} catch (SQLException e) {
			throw new JdbcException(
					"Could not get connection from datasource: " + dataSource,
					e);
		}
	}

	/**
	 * Internally gets a connection from the datasource
	 * 
	 * @param dataSource
	 *            The datasource to get the connection
	 * @return
	 * @throws SQLException
	 */
	private static Connection doGetConnection(DataSource dataSource)
			throws SQLException {
		Assert.notNull(dataSource);
		Connection connection;

		if (isTransactionActive()) {
			connection = connections.get().get(dataSource);

			if (connection == null) {
				connection = dataSource.getConnection();
				connections.get().put(dataSource, connection);
			}
		} else {
			connection = dataSource.getConnection();
		}

		return connection;
	}

	/**
	 * Checks if theres a current active transaction
	 * 
	 * @return <code>true</code> if the transaction is active
	 */
	public static boolean isTransactionActive() {
		return synchronization.get() != null;
	}

	/**
	 * Prepare resources for a transaction
	 */
	static void beginTransaction() {
		synchronization.set(new Object());
		connections.set(new WeakHashMap<DataSource, Connection>());
	}

	/**
	 * Finishes teh current active transaction
	 */
	static void endTransaction() {
		if (synchronization.get() != null) {
			synchronization.remove();
			connections.remove();
		}
	}

	/**
	 * Releases the current connection and closes it.
	 * 
	 * @param dataSource
	 *            The datasource which have providen the connection
	 * @param connection
	 *            The connection to release
	 */
	public static void releaseConnection(DataSource dataSource,
			Connection connection) {

		Assert.notNull(dataSource);
		Assert.notNull(connection);

		if (isTransactionActive()) {
			Connection connection2 = connections.get().get(dataSource);

			if (!connection.equals(connection2)) {
				throw new IllegalArgumentException(
						"The providen connection does not belongs to the providen datasource.");
			}

			connections.get().remove(dataSource);
			connection = connection2;
		}

		JdbcUtils.close(connection);
	}

}
